/**************************************************************************//**
  \file Peer2Peer.c

  \brief Peer-2-peer sample application.

  \author
    Atmel Corporation: http://www.atmel.com \n
    Support email: avr@atmel.com

  Copyright (c) 2008-2012, Atmel Corporation. All rights reserved.
  Licensed under Atmel's Limited License Agreement (BitCloudTM).

  \internal
    History:
    14.10.09 A. Taradov - Added FIFO for received packets
******************************************************************************/

/******************************************************************************
                    Includes section
******************************************************************************/
#include <types.h>
#include <taskManager.h>
#include <configServer.h>
#include <appTimer.h>
#include <zdo.h>
#include <peer2peer.h>
#include <serialInterface.h>

#if BSP_ENABLE_RS232_CONTROL == 1
#include "rs232Controller.h"
#endif // BSP_ENABLE_RS232_CONTROL

/******************************************************************************
                    Define(s) section
******************************************************************************/

/******************************************************************************
                    Variables section
******************************************************************************/
// Network related variables
static uint16_t nwkAddr;                        // Own NWK address
static AppMessageBuffer_t appMessageBuffer;     // Application message buffer
static uint8_t messageIdTx = 0;                 // Application transmitted message ID
static uint8_t messageIdRx = 0;                 // Next expected ID of received message
static uint16_t actualDataLength = 0;           // Actual data length to be transmitted via network

// Data indications FIFO related variables
static uint8_t apsDataIndFifo[APP_DATA_IND_BUFFER_SIZE];
static uint16_t apsDataIndFifoStart = 0;
static uint16_t apsDataIndFifoEnd = 0;

// USART related variables
static HAL_UsartDescriptor_t appUsartDescriptor;          // USART descriptor (required by stack)
static bool usartTxBusy = false;                          // USART transmission transaction status
static uint8_t usartTxBuffer[APP_USART_TX_BUFFER_SIZE];   // USART Tx buffer
static uint8_t usartRxBuffer[APP_USART_RX_BUFFER_SIZE];   // USART Rx buffer

// Application related parameters
static AppState_t appState = APP_INITIAL_STATE;  // application state
static AppDataTransmissionState_t appDataTransmissionState;
static ZDO_StartNetworkReq_t networkParams; // request params for ZDO_StartNetworkReq
static APS_DataReq_t apsDataReq;

// Endpoint parameters
static SimpleDescriptor_t simpleDescriptor = { APP_ENDPOINT, APP_PROFILE_ID, 1, 1, 0, 0 , NULL, 0, NULL };
static APS_RegisterEndpointReq_t endpointParams;
static bool noIndications = false;

// Timer indicating starting network during network joining.
// Also used as a delay timer between APS_DataConf and APS_DataReq.
static HAL_AppTimer_t delayTimer;

#if APP_DETECT_LINK_FAILURE == 1
static uint8_t retryCounter = 0;                // Data sending retries counter
// Leave request, used for router to leave the network when communication was interrupted
static ZDO_ZdpReq_t leaveReq;
#endif // APP_DETECT_LINK_FAILURE

/***********************************************************************************
                    Static functions declarations section
***********************************************************************************/
static void APS_DataIndication(APS_DataInd_t* dataInd);
static void APS_DataConf(APS_DataConf_t* confInfo);
static void ZDO_StartNetworkConf(ZDO_StartNetworkConf_t* confirmInfo);
static void initNetwork(void);
static void startNetwork(void);
static void networkSendData(bool newTransmission);

static void startBlinkTimer(void);
static void startingNetworkTimerFired(void);
static void delayTimerFired(void);

static void initSerialInterface(void);
static void usartStartSending(void);
static void usartBytesReceived(uint16_t readBytesLen);
static void usartWriteConf(void);

static uint16_t fifoFreeSpace(void);
static void fifoWriteData(uint8_t *data, uint16_t size);
static uint16_t fifoReadData(uint8_t *data, uint16_t size);

static void buttonReleased(uint8_t buttons);

#if APP_DETECT_LINK_FAILURE == 1
static void leaveNetwork(void);
static void zdpLeaveResp(ZDO_ZdpResp_t *zdpResp);
#endif // APP_DETECT_LINK_FAILURE

/***********************************************************************************
                    Implementation section
***********************************************************************************/
/**************************************************************************//**
  \brief Application task handler.

  \param  none.
  \return none.
******************************************************************************/
void APL_TaskHandler(void)
{
  switch (appState)
  {
    case APP_INITIAL_STATE:                 // Node has initial state
      initSerialInterface();                // Open USART
      BSP_OpenButtons(NULL, buttonReleased); // Open buttons
      BSP_OpenLeds();                        // Enable LEDs
      initNetwork();
      SYS_PostTask(APL_TASK_ID);            // Execute next step
      break;

    case APP_NETWORK_JOINING_STATE:
      startBlinkTimer();
      startNetwork();
      break;

    case APP_NETWORK_LEAVING_STATE:
    case APP_NETWORK_JOINED_STATE:
    default:
      break;
  }
}

/**************************************************************************//**
  \brief Intializes network parameters.

  \param  none.
  \return none.
******************************************************************************/
static void initNetwork(void)
{
  DeviceType_t deviceType;

  // NWK preconfigured address reading for Config Server
  CS_ReadParameter(CS_NWK_ADDR_ID, &nwkAddr);

  CS_WriteParameter(CS_NWK_UNIQUE_ADDR_ID, &(bool){true});

  // Node role detection. If nwkAddr == 0 then node is coordinator
  // otherwise node role is router.
  if (0 == nwkAddr)
  {
#ifdef _SECURITY_
    { //Set coordinator uid equal to trust center address
      ExtAddr_t extAddr;
      CS_ReadParameter(CS_APS_TRUST_CENTER_ADDRESS_ID, &extAddr);
      CS_WriteParameter(CS_UID_ID, &extAddr);
    }
#endif //_SECURITY_

    deviceType = DEVICE_TYPE_COORDINATOR;
  }
  else
  {
    deviceType = DEVICE_TYPE_ROUTER;
  }

  // Set the deviceType value to Config Server
  CS_WriteParameter(CS_DEVICE_TYPE_ID, &deviceType);

  // Start joining procedure
  appState = APP_NETWORK_JOINING_STATE;
}

/**************************************************************************//**
  \brief ZDO_StartNetwork primitive confirmation callback.

  \param  confirmInfo - confirmation parametrs.
  \return none.
******************************************************************************/
void ZDO_StartNetworkConf(ZDO_StartNetworkConf_t* confirmInfo)
{
  HAL_StopAppTimer(&delayTimer); // Stop the blink timer

  if (confirmInfo->status == ZDO_SUCCESS_STATUS)
  {
    appDataTransmissionState = APP_DATA_TRANSMISSION_READY_STATE;
    appState = APP_NETWORK_JOINED_STATE;

#if APP_DETECT_LINK_FAILURE == 1
    retryCounter = 0;
#endif // APP_DETECT_LINK_FAILURE
    actualDataLength = 0;

    BSP_OnLed(APP_NETWORK_STATUS_LED);

    // Setup timer to be used as delay timer between APS_DataConf and APS_DataReq.

    // Set application endpoint properties
    endpointParams.simpleDescriptor = &simpleDescriptor;
    endpointParams.APS_DataInd = APS_DataIndication;
    // Register endpoint
    APS_RegisterEndpointReq(&endpointParams);
  }
  else
  {
    SYS_PostTask(APL_TASK_ID);
  }
}

/**************************************************************************//**
  \brief Starts network.

  \param  none.
  \return none.
******************************************************************************/
static void startNetwork(void)
{
  networkParams.ZDO_StartNetworkConf = ZDO_StartNetworkConf;
  // start network
  ZDO_StartNetworkReq(&networkParams);
}

#if APP_DETECT_LINK_FAILURE == 1
/**************************************************************************//**
  \brief Leave network.

  \param  none.
  \return none.
******************************************************************************/
static void leaveNetwork(void)
{
  ZDO_MgmtLeaveReq_t *zdpLeaveReq = &leaveReq.req.reqPayload.mgmtLeaveReq;
  APS_UnregisterEndpointReq_t unregEndpoint;

  appState = APP_NETWORK_LEAVING_STATE;

  unregEndpoint.endpoint = endpointParams.simpleDescriptor->endpoint;
  APS_UnregisterEndpointReq(&unregEndpoint);

  leaveReq.ZDO_ZdpResp =  zdpLeaveResp;
  leaveReq.reqCluster = MGMT_LEAVE_CLID;
  leaveReq.dstAddrMode = EXT_ADDR_MODE;
  leaveReq.dstExtAddr = 0;
  zdpLeaveReq->deviceAddr = 0;
  zdpLeaveReq->rejoin = 0;
  zdpLeaveReq->removeChildren = 1;
  zdpLeaveReq->reserved = 0;
  ZDO_ZdpReq(&leaveReq);
}

/**************************************************************************//**
  \brief Leave network response.

  \param  zdpResp - response data
  \return none.
******************************************************************************/
static void zdpLeaveResp(ZDO_ZdpResp_t *zdpResp)
{
  BSP_OffLed(APP_NETWORK_STATUS_LED);

  // Try to rejoin the network
  appState = APP_NETWORK_JOINING_STATE;
  SYS_PostTask(APL_TASK_ID);

  (void)zdpResp;
}
#endif // APP_DETECT_LINK_FAILURE

/**************************************************************************//**
  \brief Starts Blink Timer based on HAL_AppTimer_t.

  \param  none.
  \return none.
******************************************************************************/
static void startBlinkTimer(void)
{
  delayTimer.interval = APP_JOINING_INDICATION_PERIOD;
  delayTimer.mode     = TIMER_REPEAT_MODE;
  delayTimer.callback = startingNetworkTimerFired;
  HAL_StartAppTimer(&delayTimer);
}

/**************************************************************************//**
  \brief Blink timer callback function.

  \param none.
  \return none.
******************************************************************************/
static void startingNetworkTimerFired(void)
{
  BSP_ToggleLed(APP_NETWORK_STATUS_LED);
}

/**************************************************************************//**
  \brief Update network status event handler.

  \param  nwkParams - new network parameters.
  \return none.
******************************************************************************/
void ZDO_MgmtNwkUpdateNotf(ZDO_MgmtNwkUpdateNotf_t *nwkParams)
{
  switch (nwkParams->status)
  {
    case ZDO_NETWORK_STARTED_STATUS:
      break;

    case ZDO_NETWORK_LOST_STATUS:
    {
      APS_UnregisterEndpointReq_t unregEndpoint;

      unregEndpoint.endpoint = endpointParams.simpleDescriptor->endpoint;
      APS_UnregisterEndpointReq(&unregEndpoint);

      BSP_OffLed(APP_NETWORK_STATUS_LED);

      // try to rejoin the network
      appState = APP_NETWORK_JOINING_STATE;
      SYS_PostTask(APL_TASK_ID);
      break;
    }

    case ZDO_NWK_UPDATE_STATUS:
      break;

    default:
      break;
  }
}

/**************************************************************************//**
  \brief Wakeup event handler (dummy).

  \param  none.
  \return none.
******************************************************************************/
void ZDO_WakeUpInd(void)
{
}

/**************************************************************************//**
  \brief Sends data to the network.

  \param newTransmission - new data transmission or retransmission

  \return none.
******************************************************************************/
static void networkSendData(bool newTransmission)
{
  if (APP_DATA_TRANSMISSION_SENDING_STATE == appDataTransmissionState)
  {
    appDataTransmissionState = APP_DATA_TRANSMISSION_BUSY_STATE;
    // indicate we're sending
    BSP_OnLed(APP_SENDING_STATUS_LED);

    if (newTransmission)
    {
      // The new applicatiom message will be sent
      // messageId value
      appMessageBuffer.message.messageId = messageIdTx++;
      // APS Data Request preparing
      apsDataReq.dstAddrMode = APS_SHORT_ADDRESS;               // Short addressing mode
      // Destination address
      apsDataReq.dstAddress.shortAddress = nwkAddr ^ 1;
      apsDataReq.profileId = simpleDescriptor.AppProfileId;     // Profile ID
      apsDataReq.dstEndpoint = simpleDescriptor.endpoint;       // Desctination endpoint
      apsDataReq.clusterId = APP_CLUSTER_ID;                    // Desctination cluster ID
      apsDataReq.srcEndpoint = simpleDescriptor.endpoint;       // Source endpoint
      apsDataReq.asdu = (uint8_t*) &appMessageBuffer.message;   // application message pointer
      // actual application message length
      apsDataReq.asduLength = actualDataLength + sizeof(appMessageBuffer.message.messageId);
      apsDataReq.txOptions.acknowledgedTransmission = 1;        // Acknowledged transmission enabled
#if APP_FRAGMENTATION
      apsDataReq.txOptions.fragmentationPermitted = 1;
#else
      apsDataReq.txOptions.fragmentationPermitted = 0;
#endif // APP_FRAGMENTATION
      apsDataReq.radius = 0;                                    // Use maximal possible radius
      apsDataReq.APS_DataConf = APS_DataConf;                   // Confirm handler
    }

    APS_DataReq(&apsDataReq);
  }
}

/**************************************************************************//**
  \brief Handler of aps data sent confirmation.

  \param  confInfo - confirmation info
  \return none.
******************************************************************************/
void APS_DataConf(APS_DataConf_t* confInfo)
{
  BSP_OffLed(APP_SENDING_STATUS_LED);

  if (APS_SUCCESS_STATUS != confInfo->status)
  {
#if APP_DETECT_LINK_FAILURE == 1
    retryCounter++;
    if (MAX_RETRIES_BEFORE_REJOIN == retryCounter)
    {
      leaveNetwork();
    }
    else
#endif // APP_DETECT_LINK_FAILURE
    {
      // Data not delivered, resend.
      appDataTransmissionState = APP_DATA_TRANSMISSION_SENDING_STATE;
      networkSendData(false);
    }
    return;
  }

#if APP_DETECT_LINK_FAILURE == 1
  retryCounter = 0;
#endif // APP_DETECT_LINK_FAILURE
  actualDataLength = 0;

#if APP_TRANSMISSION_DELAY > 0
  appDataTransmissionState = APP_DATA_TRANSMISSION_STOP_STATE;

  delayTimer.interval = APP_TRANSMISSION_DELAY;
  delayTimer.mode     = TIMER_ONE_SHOT_MODE;
  delayTimer.callback = delayTimerFired;
  HAL_StartAppTimer(&delayTimer);
#else
  appDataTransmissionState = APP_DATA_TRANSMISSION_READY_STATE;
  usartBytesReceived(0);
#endif
}

/**************************************************************************//**
  \brief Delay timer callback function.

  \param  none.
  \return none.
******************************************************************************/
static void delayTimerFired(void)
{
  if (APP_DATA_TRANSMISSION_STOP_STATE == appDataTransmissionState)
    appDataTransmissionState = APP_DATA_TRANSMISSION_READY_STATE;
  else
    appDataTransmissionState = APP_DATA_TRANSMISSION_SENDING_STATE;
  usartBytesReceived(0);
}

/**************************************************************************//**
  \brief APS data indication handler.

  \param  indData - received data pointer.
  \return none.
******************************************************************************/
void APS_DataIndication(APS_DataInd_t* indData)
{
  AppMessage_t *appMessage = (AppMessage_t *) indData->asdu;

  // Data received indication
  BSP_OnLed(APP_RECEIVING_STATUS_LED);

  if (appMessage->messageId == messageIdRx)
  {
    fifoWriteData(appMessage->data, indData->asduLength - 1);

    usartStartSending();

    if (fifoFreeSpace() < APP_APS_PAYLOAD_SIZE)
    {
      APS_StopEndpointIndication(APP_ENDPOINT); // Disable APS Data Indications
      noIndications = true;
    }
  }

  messageIdRx = appMessage->messageId + 1;

  BSP_OffLed(APP_RECEIVING_STATUS_LED);
}

/**************************************************************************//**
  \brief Initializes USART or VCP (depends on user's settings).

  \param  none.
  \return none.
******************************************************************************/
static void initSerialInterface(void)
{
  usartTxBusy = false;

  appUsartDescriptor.tty             = USART_CHANNEL;
  appUsartDescriptor.mode            = USART_MODE_ASYNC;
  appUsartDescriptor.baudrate        = USART_BAUDRATE_38400;
  appUsartDescriptor.dataLength      = USART_DATA8;
  appUsartDescriptor.parity          = USART_PARITY_NONE;
  appUsartDescriptor.stopbits        = USART_STOPBIT_1;
  appUsartDescriptor.rxBuffer        = usartRxBuffer;
  appUsartDescriptor.rxBufferLength  = sizeof(usartRxBuffer);
  appUsartDescriptor.txBuffer        = NULL; // use callback mode
  appUsartDescriptor.txBufferLength  = 0;
  appUsartDescriptor.rxCallback      = usartBytesReceived;
  appUsartDescriptor.txCallback      = usartWriteConf;
  appUsartDescriptor.flowControl     = USART_FLOW_CONTROL_HARDWARE;

#if BSP_ENABLE_RS232_CONTROL == 1
  BSP_EnableRs232();
#endif // BSP_ENABLE_RS232_CONTROL

  OPEN_USART(&appUsartDescriptor);
}

/**************************************************************************//**
  \brief Initiates data transmission via serial interface.

  \param  none
  \return none
******************************************************************************/
static void usartStartSending(void)
{
  uint16_t size;

  if (!usartTxBusy)
  {
    size = fifoReadData(usartTxBuffer, APP_USART_TX_BUFFER_SIZE);
    WRITE_USART(&appUsartDescriptor, usartTxBuffer, size);

    usartTxBusy = true;

    if (noIndications && (fifoFreeSpace() >= APP_APS_PAYLOAD_SIZE))
    {
      APS_ResumeEndpointIndication(APP_ENDPOINT); // Enable APS Data Indications
      noIndications = false;
    }
  }
}

/**************************************************************************//**
  \brief Writing confirmation has been received. New message can be sent.

  \param  none
  \return none
******************************************************************************/
static void usartWriteConf(void)
{
  usartTxBusy = false;

  if (APP_DATA_IND_BUFFER_SIZE != fifoFreeSpace())
    usartStartSending();
}

/**************************************************************************//**
  \brief New USART bytes are received - HAL USART callback function.

  \param  bytesToRead - received bytes amount.
  \return none.
******************************************************************************/
static void usartBytesReceived(uint16_t bytesToRead)
{
  if (APP_NETWORK_JOINED_STATE != appState)
    return;
  if ((APP_DATA_TRANSMISSION_BUSY_STATE == appDataTransmissionState)
    || (APP_DATA_TRANSMISSION_STOP_STATE == appDataTransmissionState))
    return;

  if (actualDataLength < APP_MAX_PACKET_SIZE)
  {
    actualDataLength += (uint8_t) READ_USART(&appUsartDescriptor,
      appMessageBuffer.message.data + actualDataLength,
      APP_MAX_PACKET_SIZE - actualDataLength);
  }

#if APP_DELAY_BEFORE_SEND > 0
  if (actualDataLength < APP_MAX_PACKET_SIZE)
  {
    if ((APP_DATA_TRANSMISSION_READY_STATE == appDataTransmissionState) &&
        (actualDataLength > 0))
    {
      appDataTransmissionState = APP_DATA_TRANSMISSION_WAIT_STATE;
      delayTimer.interval = APP_DELAY_BEFORE_SEND;
      delayTimer.mode     = TIMER_ONE_SHOT_MODE;
      delayTimer.callback = delayTimerFired;
      HAL_StartAppTimer(&delayTimer);
      return;
    }
    if (APP_DATA_TRANSMISSION_WAIT_STATE == appDataTransmissionState)
      return;
  }
  else if (APP_DATA_TRANSMISSION_WAIT_STATE == appDataTransmissionState)
    HAL_StopAppTimer(&delayTimer);
#endif


  if (actualDataLength > 0)
  {
    appDataTransmissionState = APP_DATA_TRANSMISSION_SENDING_STATE;
    networkSendData(true);
  }

  (void)bytesToRead;
}

/**************************************************************************//**
  \brief Get free space size in data indications FIFO

  \param  none
  \return Free space size in FIFO
******************************************************************************/
static uint16_t fifoFreeSpace(void)
{
  int16_t free = apsDataIndFifoStart - apsDataIndFifoEnd;

  if (apsDataIndFifoStart <= apsDataIndFifoEnd)
    free += sizeof(apsDataIndFifo);

  return free;
}

/**************************************************************************//**
  \brief Write data into data indications FIFO. FIFO must have enough
    space to fit all data.

  \param  data - pointer to the data
          size - size of provided data (bytes)
  \return none
******************************************************************************/
static void fifoWriteData(uint8_t *data, uint16_t size)
{
  uint16_t i;

  for (i = 0; i < size; i++)
  {
    apsDataIndFifo[apsDataIndFifoEnd++] = data[i];
    if (apsDataIndFifoEnd == sizeof(apsDataIndFifo))
      apsDataIndFifoEnd = 0;
  }
}

/**************************************************************************//**
  \brief Read data from data indications FIFO

  \param  data - pointer to the buffer where data should be placed
          size - maximum size of data to read
  \return Actual amount of data read
******************************************************************************/
static uint16_t fifoReadData(uint8_t *data, uint16_t size)
{
  uint16_t read = 0;
  uint16_t i;

  for (i = 0; i < size; i++)
  {
    if (apsDataIndFifoStart == apsDataIndFifoEnd)
      break;

    data[i] = apsDataIndFifo[apsDataIndFifoStart++];
    read++;

    if (apsDataIndFifoStart == sizeof(apsDataIndFifo))
      apsDataIndFifoStart = 0;
  }

  return read;
}

/**************************************************************************//**
  \brief Button released event handler.

  \param  aButton - released button number
  \return none.
******************************************************************************/
static void buttonReleased(uint8_t aButton)
{
  switch (aButton)
  {
    case BSP_KEY0:  // SW1 pressed
      if ((APP_NETWORK_JOINED_STATE == appState) &&
          (APP_DATA_TRANSMISSION_READY_STATE == appDataTransmissionState))
      {
        // Fill the buffer with some data and send it
        memset(&appMessageBuffer.message.data, 0xdd, APP_MAX_PACKET_SIZE);
        actualDataLength = APP_MAX_PACKET_SIZE;
        appDataTransmissionState = APP_DATA_TRANSMISSION_SENDING_STATE;
        networkSendData(true);
        break;
      }

    default:
      break;
  }
}
#ifdef _BINDING_
/***********************************************************************************
  Stub for ZDO Binding Indication

  Parameters:
    bindInd - indication

  Return:
    none

 ***********************************************************************************/
void ZDO_BindIndication(ZDO_BindInd_t *bindInd)
{
  (void)bindInd;
}

/***********************************************************************************
  Stub for ZDO Unbinding Indication

  Parameters:
    unbindInd - indication

  Return:
    none

 ***********************************************************************************/
void ZDO_UnbindIndication(ZDO_UnbindInd_t *unbindInd)
{
  (void)unbindInd;
}
#endif //_BINDING_

/**********************************************************************//**
  \brief Main - C program main start function

  \param none
  \return none
**************************************************************************/
int main(void)
{
  SYS_SysInit();

  for(;;)
  {
    SYS_RunTask();
  }
}
// eof peer2Peer.c
